﻿using ARSoft.Tools.Net;
using ARSoft.Tools.Net.Dns;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;

namespace DomainNameServer.Models
{
    public class DomainInfo : INotifyPropertyChanged
    {
        public static Regex regEx;
        /// <summary>
        /// 域名
        /// </summary>
        public string Domain { get; set; }

        public bool Enabled { get; set; }

        public DomainInfo(string domain)
        {
            this.Domain = domain.ToLower();
            regEx = new Regex(@"(\w.+)." + this.Domain);
        }
        /// <summary>
        /// 域名解析记录
        /// </summary>
        public List<RecordInfo> RecordList = new List<RecordInfo>();

        public event PropertyChangedEventHandler PropertyChanged;

        public Guid AddRecord(RecordType type, string record, string recordValue, int? mxPriority = null)
        {
            var ID = Guid.NewGuid();
            RecordList.Add(new RecordInfo { RecordType = type, Host = record, ResoluteTo = recordValue, MX优先级 = mxPriority, Enabled = true });
            return ID;
        }

        /// <summary>
        /// 尝试将域名解析为IP
        /// </summary>
        /// <param name="domain"></param>
        /// <param name="dns"></param>
        /// <param name="type"></param>
        /// <param name="tryTimes"></param>
        /// <returns></returns>
        public void Resolute(QueryReceivedEventArgs dns)
        {
            RecordList = RecordList.Where(x => x != null).ToList();
            foreach (var item in ((DnsMessage)dns.Query).Questions)
            {
                ResoluteInternal(dns, item);
            }
        }

        private void ResoluteInternal(QueryReceivedEventArgs dns, DnsQuestion query)
        {

            var response = (DnsMessage)dns.Response;
            string host;
            if (TryGetHost(query.Name.ToString(), out host))
            {
                if ((query.RecordType == RecordType.A || query.RecordType == RecordType.Aaaa))
                {

                    ResoluteARecord(dns, query, host);
                    ResoluteCNAMERecord(dns, query, host);
                }
                else if (query.RecordType == RecordType.CName)
                {
                    ResoluteCNAMERecord(dns, query, host);
                }
                else if (query.RecordType == RecordType.Mx)
                {
                    ResoluteMXRecord(dns, query, host);
                }
                else if (query.RecordType == RecordType.Ns)
                {
                    ResoluteNSRecord(dns, query, host);
                }
                else if (query.RecordType == RecordType.Ptr)
                {
                    ResolutePTRRecord(dns, query);
                }
                else
                {
                    response.ReturnCode = ReturnCode.NotImplemented;
                }
            }
        }

        private void ResolutePTRRecord(QueryReceivedEventArgs dns, DnsQuestion query)
        {
            var ip = query.Name.ToString();
            var record = this.RecordList.Find(x => x.Enabled && x.RecordType == RecordType.Ptr && x.Host == ip);
            if (record != null)
            {
                ((DnsMessage)dns.Response).AnswerRecords.Add(new PtrRecord(query.Name, 100, DomainName.Parse(record.ResoluteTo)));
            }
        }

        private void ResoluteCNAMERecord(QueryReceivedEventArgs dns, DnsQuestion query, string queryHost)
        {
            //queryHost could be 'abc' or 'abc.123' or '*'
            string cname = null;
            var record = RecordList.Find(x => x.RecordType == RecordType.CName && x.Host == queryHost && x.Enabled);
            if (record != null)
            {
                cname = record.ResoluteTo;
            }
            else
            {
                //when generic resolution '*'
                record = RecordList.Find(x => x.RecordType == RecordType.CName && x.Host == "*" && x.Enabled);
                if (record != null)
                {
                    cname = record.ResoluteTo;
                }
            }
            if (cname != null)
            {
                ((DnsMessage)dns.Response).AnswerRecords.Add(new CNameRecord(query.Name, 100, DomainName.Parse(cname)));
            }
            else
            {
                //tell client that this server does not do an recursive query
                ((DnsMessage)dns.Response).IsRecursionAllowed = true;
            }
        }

        private void ResoluteNSRecord(QueryReceivedEventArgs dns, DnsQuestion query, string queryHost)
        {
            //queryHost could be 'abc' or 'abc.123' or '*'
            string ns = null;
            var record = RecordList.Find(x => x.RecordType == RecordType.Ns && x.Host == queryHost && x.Enabled);
            if (record != null)
            {
                ns = record.ResoluteTo;
                ((DnsMessage)dns.Response).AnswerRecords.Add(new NsRecord(query.Name, 100, DomainName.Parse(ns)));
            }
        }

        private void ResoluteMXRecord(QueryReceivedEventArgs dns, DnsQuestion query, string queryHost)
        {
            //queryHost could be 'abc' or 'abc.123' or '*'
            string domainName = null;
            var record = RecordList.Find(x => x.RecordType == RecordType.Mx && x.Host == queryHost && x.Enabled);
            if (record != null)
            {
                domainName = record.ResoluteTo;
            }
            else
            {
                //when generic resolution '*'
                record = RecordList.Find(x => x.RecordType == RecordType.Mx && x.Host == "*" && x.Enabled);
                if (record != null)
                {
                    domainName = record.ResoluteTo;
                }
            }
            if (domainName != null)
            {
                ((DnsMessage)dns.Response).AnswerRecords.Add(new MxRecord(query.Name, 100, 1, DomainName.Parse(domainName)));
            }
            else
            {
                ((DnsMessage)dns.Response).IsRecursionAllowed = false;
            }
        }

        private void ResoluteARecord(QueryReceivedEventArgs dns, DnsQuestion query, string queryHost)
        {
            //queryHost could be 'abc' or 'abc.123' or '@' or '*'
            string ip = null;

            var record = RecordList.Find(x => (x.RecordType == RecordType.A || x.RecordType == RecordType.Aaaa) && x.Host == queryHost && x.Enabled);
            if (record != null)
            {
                ip = record.ResoluteTo;
            }
            else
            {
                //when generic resolution '*'
                record = RecordList.Find(x => (x.RecordType == RecordType.A || x.RecordType == RecordType.Aaaa) && x.Host == "*" && x.Enabled);
                if (record != null)
                {
                    ip = record.ResoluteTo;
                }
            }
            if (ip != null)
            {
                ((DnsMessage)dns.Response).AnswerRecords.Add(new ARecord(query.Name, 100, IPAddress.Parse(ip)));
            }
        }

        private bool TryGetHost(string input, out string host)
        {
            host = null;
            input = (input.EndsWith(".") ? input.TrimEnd('.') : input).ToLower();
            if (input.Equals(Domain))
            {
                host = "@";
                return true;
            }
            else
            {
                Match match = null;
                if ((match = regEx.Match(input)).Success)
                {
                    host = match.Groups[1].Value;
                }
            }
            return !String.IsNullOrEmpty(host);
        }

        private RecordType GetRecordType(string input)
        {
            if (input == "@")
            {
                return RecordType.A;
            }
            else if (input.EndsWith("."))
            {
                return RecordType.Mx;
            }
            else
            {
                return RecordType.CName;
            }
        }

        public override string ToString()
        {
            return (this.Enabled ? "" : "[OFF]") + " " + Domain;
        }
    }
}
